//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2023-10-19     ZhangYihua   first version
// 2024-07-11     ZhangYihua   add data back-to-back mode
//
// Description  : SPI slave interface, MSB first, format
//                bit num |     1      |   ADDR_LEN   | RSV_LEN |    DATA_LEN   |            DATA_LEN   |
//                        |-----------------------------------------------------|        ---------------|
//                sdi     | wr/rd flag |   address    | latency |    wr data    | ~~~~     back-to-back |
//                sdo                                           |    rd data    |              data     |
//                        |-----------------------------------------------------|        ---------------|
//                        |   (1/0)    |   MSB first  |         |   MSB first   |           MSB first   |
//################################################################################

module spi_slv #(
parameter           CPOL                    = 1'b1, // SCK is 1'b0:LOW; 1'b1:HIGH; when idle
parameter           CPHA                    = 1'b1, // capture SDO/SDI at 1'b0:1st; 1'b1:2nd SCK edge
parameter           WR_IND                  = 1'b1,
parameter           ADDR_LEN                = 15,   // address bits length (must ADDR_LEN>=3)
parameter           RSV_LEN                 = 0,    // reserved for read latency if frequency of sck is too high
parameter           DATA_LEN                = 8,    // data bits length
parameter           CLK_NUM                 = 1,    // register clock domain number(must CLK_NUM>=1)
parameter           SYNC_NUM                = 2     // synchronization pipeline number from sck -> clk
) ( 
input                                       csn,
input                                       sck,
input                                       sdi,
output                                      sdo,
output  reg                                 sdoe,

output  reg                                 srst,   // write 8'b1001xxxx at address 0 to generate single soft reset
// Register Access Bus
// full supported reg   : RW, RW_PLS, W1_PLS, RW_SC, HHL_WC, IAA2MEM
// almost supported reg : HHL_RC(probably clear unread event), BID_RW(read data metastable)
// unsuppported reg     : *CNT_*C, MAX_RC, MIN_RC
input               [CLK_NUM-1:0]           clk,

output              [CLK_NUM-1:0]           rab_en,     // single pulse means a R/W access start
output              [CLK_NUM-1:0]           rab_wr,     // 1'b1: write; 1'b0: read, one clk width
output              [ADDR_LEN-1:0]          rab_addr,
output              [DATA_LEN-1:0]          rab_wdat,
input               [DATA_LEN-1:0]          rab_rdat,   // no read latency(combinational logic), keep stable until rab_addr changed
output              [CLK_NUM-1:0]           rab_ract,   // keep read regs unchanged when rab_ract==1'b1
output              [CLK_NUM-1:0]           rab_rhld    // latch read regs when rab_rhld==1'b1
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          RD_IND                  = ~WR_IND;
localparam          FRM_LEN                 = 1 + ADDR_LEN + RSV_LEN + DATA_LEN;
localparam          FRM_CW                  = $clog2(FRM_LEN);
localparam [FRM_CW-1:0]     FRM_ZERO        = 0;
localparam [FRM_CW-1:0]     FRM_FULL        = FRM_LEN-1;
localparam [FRM_CW-1:0]     ADDR_START      = 1+1-1;
localparam [FRM_CW-1:0]     ADDR_END        = 1+ADDR_LEN-1;
localparam [FRM_CW-1:0]     DATA_START      = 1+ADDR_LEN+RSV_LEN+1-1;
localparam [FRM_CW-1:0]     DATA_4TH        = 1+ADDR_LEN+RSV_LEN+4-1;
localparam          RS_LEN                  = (ADDR_LEN>=DATA_LEN) ? ADDR_LEN-1 : DATA_LEN-1;

wire                                        clk_cap;
wire                                        clk_gen;
reg                 [FRM_CW-1:0]            cnt_frm;
reg                                         wr_flg;
wire                                        rs_en;
reg                 [RS_LEN-1:0]            rx_shift;
reg                 [DATA_LEN-1:0]          rdat_shift;

reg                                         spi_ftog;
reg                                         spi_ract;
reg                                         spi_rtog;
reg                                         spi_wtog;
reg                 [ADDR_LEN-1:0]          spi_addr;
reg                 [DATA_LEN-1:0]          spi_wdat;
wire                [DATA_LEN-1:0]          spi_rdat;

wire                [CLK_NUM-1:0]           spi_wtog_s;
wire                [CLK_NUM-1:0]           spi_rtog_s;
wire                [CLK_NUM-1:0]           spi_ftog_s;
reg                 [CLK_NUM-1:0]           spi_wtog_1d;
reg                 [CLK_NUM-1:0]           spi_rtog_1d;
reg                 [CLK_NUM-1:0]           spi_ftog_1d;
wire                [CLK_NUM-1:0]           spi_wen;
wire                [CLK_NUM-1:0]           spi_ren;

//################################################################################
// main
//################################################################################
                                                        // capture sdi/sdo at edge of SCK,
assign clk_cap = (CPOL==1'b0 && CPHA==1'b0) ?  sck :    // 1st rising edge
                 (CPOL==1'b0 && CPHA==1'b1) ? ~sck :    // 2nd falling edge
                 (CPOL==1'b1 && CPHA==1'b0) ? ~sck :    // 1st falling edge
                                               sck;     // 2nd rising edge
assign clk_gen = ~clk_cap;      // generate read data at inverse edge of clk_cap

always@(posedge clk_cap or posedge csn) begin
    if (csn==1'b1) begin
        cnt_frm <=`U_DLY {FRM_CW{1'b0}};
    end else begin
//        cnt_frm <=`U_DLY (cnt_frm>=FRM_FULL) ? {FRM_CW{1'b0}} : cnt_frm + 1'd1;
        cnt_frm <=`U_DLY (cnt_frm>=FRM_FULL) ? DATA_START : cnt_frm + 1'd1;     // support data back-to-back mode
    end
end

always@(posedge clk_cap) begin
    if (cnt_frm==FRM_ZERO) begin
        wr_flg <=`U_DLY sdi;
    end
end

// hold data for fetch
always@(posedge clk_cap) begin
    if (cnt_frm==FRM_ZERO && sdi==RD_IND) begin
        spi_ftog <=`U_DLY ~spi_ftog;
    end
end

always@(posedge clk_cap or posedge csn) begin
    if (csn==1'b1) begin
        spi_ract <=`U_DLY 1'b0;
    end else begin
        if (cnt_frm==FRM_ZERO && sdi==RD_IND) begin
            spi_ract <=`U_DLY 1'b1;
        end else if (cnt_frm==DATA_START) begin
            spi_ract <=`U_DLY 1'b0;
        end
    end
end

assign rs_en = (cnt_frm>=ADDR_START && cnt_frm<ADDR_END)                   ? 1'b1 :     // addr shift
               (cnt_frm>=DATA_START && cnt_frm<FRM_FULL && wr_flg==WR_IND) ? 1'b1 :     // wdata shift
                                                                             1'b0;
always@(posedge clk_cap) begin
    if (rs_en==1'b1) begin
        rx_shift <=`U_DLY {rx_shift[RS_LEN-2:0], sdi};
    end
end

always@(posedge clk_cap) begin
    if (cnt_frm==ADDR_END) begin
        spi_addr = {rx_shift[ADDR_LEN-2:0], sdi};
    end
end

always@(posedge clk_cap) begin
    if (cnt_frm==FRM_FULL && wr_flg==WR_IND) begin
        spi_wdat <=`U_DLY {rx_shift[DATA_LEN-2:0], sdi};
        spi_wtog <=`U_DLY ~spi_wtog;
    end
end

// global soft reset register
always@(posedge clk_cap or posedge csn) begin
    if (csn==1'b1) begin
        srst <=`U_DLY 1'b0;
    end else if (wr_flg==WR_IND && spi_addr=={ADDR_LEN{1'b0}} && cnt_frm==DATA_4TH && {rx_shift[2:0], sdi}==4'b1001) begin
        srst <=`U_DLY 1'b1;
    end
end

always@(posedge clk_gen) begin
    if (cnt_frm==DATA_START && wr_flg==RD_IND) begin
        rdat_shift <=`U_DLY spi_rdat;
        spi_rtog <=`U_DLY ~spi_rtog;
    end else if (cnt_frm>DATA_START && wr_flg==RD_IND) begin
        rdat_shift <=`U_DLY {rdat_shift[DATA_LEN-2:0], 1'b1};
    end else
        ;
end
assign sdo = rdat_shift[DATA_LEN-1];

always@(posedge clk_gen or posedge csn) begin
    if (csn==1'b1) begin
        sdoe <=`U_DLY 1'b0;
    end else if (cnt_frm==DATA_START && wr_flg==RD_IND) begin
        sdoe <=`U_DLY 1'b1;
    end else if (cnt_frm==FRM_ZERO) begin
        sdoe <=`U_DLY 1'b0;
    end
end

// synopsys translate_off
// for simulation only, don't care initial value in fact
initial begin
    wr_flg = 1'b0;
    spi_rtog = 1'b0;
    spi_wtog = 1'b0;
    spi_ftog = 1'b0;
    rx_shift = {RS_LEN{1'b0}};
    spi_addr = {ADDR_LEN{1'b0}};
    spi_wdat = {DATA_LEN{1'b0}};
    rdat_shift = {DATA_LEN{1'b0}};
end
// synopsys translate_on

genvar g0;
generate for (g0=0; g0<CLK_NUM; g0=g0+1) begin:G_SYNC
    
    sync_dff #(
        .SYNC_NUM                       (SYNC_NUM                       ),	// synchronization pipeline number for each bit
        .BW                             (4                              ) 	// bit width
    ) u_sync_tog ( 
        .rst_n                          (1'b1                           ),
        .clk                            (clk[g0]                        ),

        .d                              ({spi_wtog      , spi_rtog      , spi_ftog      , spi_ract    }),	// based on raw clock domain, forbid combinational logic input
        .q                              ({spi_wtog_s[g0], spi_rtog_s[g0], spi_ftog_s[g0], rab_ract[g0]})	// based on clk domain, synchronization is completed
    );

    always@(posedge clk[g0]) begin
        spi_wtog_1d[g0] <=`U_DLY spi_wtog_s[g0];
        spi_rtog_1d[g0] <=`U_DLY spi_rtog_s[g0];
        spi_ftog_1d[g0] <=`U_DLY spi_ftog_s[g0];
    end

    assign spi_wen[g0] = spi_wtog_1d[g0] ^ spi_wtog_s[g0];
    assign spi_ren[g0] = spi_rtog_1d[g0] ^ spi_rtog_s[g0];

    assign rab_en[g0] = spi_wen[g0] | spi_ren[g0];
    assign rab_wr[g0] = spi_wen[g0];
    assign rab_rhld[g0] = spi_ftog_1d[g0] ^ spi_ftog_s[g0];

end endgenerate

assign rab_addr = spi_addr;
assign rab_wdat = spi_wdat;
assign spi_rdat = rab_rdat;

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
